package com.badoo.mobile.util;

import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;

import java.lang.ref.WeakReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class WeakHandler {
    private final ExecHandler mExec;
    private Lock mLock = new ReentrantLock();
    @SuppressWarnings("ConstantConditions")
    final ChainedRef mRunnables = new ChainedRef(mLock, null);

    public WeakHandler(EventRunner runner) {
        mExec = new ExecHandler(runner);
    }

    public void postTask(Runnable task, long delayTime, EventHandler.Priority priority) {
        mExec.postTask(wrapRunnable(task), delayTime, priority);
    }

    public void postTask(Runnable task) {
        mExec.postTask(wrapRunnable(task));
    }

    public void postTask(Runnable task, long delayTime) {
        mExec.postTask(wrapRunnable(task), delayTime);
    }

    public void postTask(Runnable task, EventHandler.Priority priority) {
        mExec.postTask(wrapRunnable(task), priority);
    }

    public void postTask(Runnable task, Object object) {
        mExec.postTask(wrapRunnable(task), object);
    }

    public void postSyncTask(Runnable task, EventHandler.Priority priority) {
        mExec.postSyncTask(wrapRunnable(task), priority);
    }

    public void postSyncTask(Runnable task) {
        mExec.postSyncTask(wrapRunnable(task));
    }

    public void postTimingTask(Runnable task, long taskTime, EventHandler.Priority priority) {
        mExec.postTimingTask(wrapRunnable(task), taskTime, priority);
    }

    public void postTimingTask(Runnable task, Object obj, long taskTime) {
        mExec.postTimingTask(wrapRunnable(task), obj, taskTime);
    }

    public void postTimingTask(Runnable task, long taskTime) {
        mExec.postTimingTask(wrapRunnable(task), taskTime);
    }

    public void removeTask(Runnable task) {
        final WeakRunnable runnable = mRunnables.remove(task);
        if (runnable != null) {
            mExec.removeTask(runnable);
        }
    }

    public void removeTask(Runnable task, Object object) {
        final WeakRunnable runnable = mRunnables.remove(task);
        if (runnable != null) {
            mExec.removeTask(runnable, object);
        }
    }

    public final boolean isIdle() {
        return mExec.isIdle();
    }

    public final EventRunner getEventRunner() {
        return mExec.getEventRunner();
    }

    private WeakRunnable wrapRunnable(Runnable r) {
        // noinspection ConstantConditions
        if (r == null) {
            throw new NullPointerException("Runnable can't be null");
        }
        ChainedRef hardRef = new ChainedRef(mLock, r);
        mRunnables.insertAfter(hardRef);
        return hardRef.wrapper;
    }

    private static class ExecHandler extends EventHandler {
        ExecHandler(EventRunner runner) {
            super(runner);
        }
    }

    static class WeakRunnable implements Runnable {
        private final WeakReference<Runnable> mDelegate;
        private final WeakReference<ChainedRef> mReference;

        WeakRunnable(WeakReference<Runnable> delegate, WeakReference<ChainedRef> reference) {
            mDelegate = delegate;
            mReference = reference;
        }

        @Override
        public void run() {
            final Runnable delegate = mDelegate.get();
            final ChainedRef reference = mReference.get();
            if (reference != null) {
                reference.remove();
            }
            if (delegate != null) {
                delegate.run();
            }
        }
    }

    static class ChainedRef {
        ChainedRef next;
        ChainedRef prev;
        final WeakReference<Runnable> runnable;
        final WeakRunnable wrapper;
        Lock lock;

        ChainedRef(Lock lock, Runnable r) {
            this.runnable = new WeakReference<>(r);
            this.lock = lock;
            this.wrapper = new WeakRunnable(new WeakReference<>(r), new WeakReference<>(this));
        }

        public void insertAfter(ChainedRef candidate) {
            lock.lock();
            try {
                if (this.next != null) {
                    this.next.prev = candidate;
                }

                candidate.next = this.next;
                this.next = candidate;
                candidate.prev = this;
            } finally {
                lock.unlock();
            }
        }

        public WeakRunnable remove() {
            lock.lock();
            try {
                if (prev != null) {
                    prev.next = next;
                }
                if (next != null) {
                    next.prev = prev;
                }
                prev = null;
                next = null;
            } finally {
                lock.unlock();
            }
            return wrapper;
        }

        public WeakRunnable remove(Runnable obj) {
            lock.lock();
            try {
                ChainedRef curr = this.next; // Skipping head
                while (curr != null) {
                    if (curr.runnable.get() == obj) { // We do comparison exactly how Handler does inside
                        return curr.remove();
                    }
                    curr = curr.next;
                }
            } finally {
                lock.unlock();
            }
            return null;
        }
    }
}
